Skip to content

Comments

fix(Admin API): Sort features by overrides#6722

Open
emyller wants to merge 7 commits intomainfrom
fix/sort-features-by-overrides
Open

fix(Admin API): Sort features by overrides#6722
emyller wants to merge 7 commits intomainfrom
fix/sort-features-by-overrides

Conversation

@emyller
Copy link
Contributor

@emyller emyller commented Feb 16, 2026

Closes #6188.

Prioritise displaying overriden features in the dashboard.

For segment overrides:

image

For identity overrides:

image

Review effort: 2/5

@emyller emyller self-assigned this Feb 16, 2026
@emyller emyller requested review from a team as code owners February 16, 2026 21:49
@emyller emyller requested review from gagantrivedi and kyle-ssg and removed request for a team February 16, 2026 21:49
@vercel
Copy link

vercel bot commented Feb 16, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
flagsmith-frontend-preview Ready Ready Preview, Comment Feb 20, 2026 10:09pm
flagsmith-frontend-staging Ready Ready Preview, Comment Feb 20, 2026 10:09pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Ignored Ignored Preview Feb 20, 2026 10:09pm

Request Review

@github-actions github-actions bot added front-end Issue related to the React Front End Dashboard api Issue related to the REST API fix labels Feb 16, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Feb 16, 2026

Docker builds report

Image Build Status Security report
ghcr.io/flagsmith/flagsmith-api-test:pr-6722 Finished ✅ Skipped
ghcr.io/flagsmith/flagsmith-e2e:pr-6722 Finished ✅ Skipped
ghcr.io/flagsmith/flagsmith-api:pr-6722 Finished ✅ Results
ghcr.io/flagsmith/flagsmith:pr-6722 Finished ✅ Results
ghcr.io/flagsmith/flagsmith-private-cloud:pr-6722 Finished ✅ Results
ghcr.io/flagsmith/flagsmith-frontend:pr-6722 Finished ✅ Results

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

This is the final PR Bugbot will review for you during this billing cycle

Your free Bugbot reviews will reset on March 10

Details

Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

@codecov
Copy link

codecov bot commented Feb 16, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.23%. Comparing base (79b7724) to head (71738c0).
⚠️ Report is 13 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #6722   +/-   ##
=======================================
  Coverage   98.23%   98.23%           
=======================================
  Files        1311     1313    +2     
  Lines       48474    48560   +86     
=======================================
+ Hits        47617    47703   +86     
  Misses        857      857           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@emyller emyller marked this pull request as draft February 17, 2026 16:35
@github-actions github-actions bot added fix and removed fix labels Feb 17, 2026
@github-actions github-actions bot added fix and removed fix labels Feb 17, 2026
@emyller emyller marked this pull request as ready for review February 17, 2026 17:52
@github-actions github-actions bot added fix and removed fix labels Feb 17, 2026
Copy link
Contributor

@Zaimwa9 Zaimwa9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a non-blocking question and the e2e are failing so it's gonna stale the approval anyways, i'll run them to see what's going on

 ✖ Segment-part-3
  
     1) AssertionError: waitForElementVisible([data-test="user-feature-switch-1-off"]): expected false to be truthy
  
        Browser: Firefox 143.0 / Debian 13.3
  
           48 |
           49 |export const waitForElementVisible = async (selector: string) => {
           50 |  logUsingLastSection(`Waiting element visible ${selector}`)
           51 |  return t
           52 |    .expect(Selector(selector).visible)
         > 53 |    .ok(`waitForElementVisible(${selector})`, { timeout: LONG_TIMEOUT })
           54 |}
           55 |
           56 |export const waitForElementNotClickable = async (selector: string) => {
           57 |  logUsingLastSection(`Waiting element visible ${selector}`)
           58 |  await t
  
           at <anonymous> (/srv/flagsmith/e2e/helpers.cafe.ts:53:6)
           at step (/srv/flagsmith/e2e/helpers.cafe.ts:33:23)
           at Object.next (/srv/flagsmith/e2e/helpers.cafe.ts:14:53)
           at <anonymous> (/srv/flagsmith/e2e/helpers.cafe.ts:8:71)
           at __awaiter (/srv/flagsmith/e2e/helpers.cafe.ts:4:12)
           at waitForElementVisible (/srv/flagsmith/e2e/helpers.cafe.ts:49:61)
           at <anonymous> (/srv/flagsmith/e2e/tests/segment-test.ts:261:30)
           at step (/srv/flagsmith/e2e/tests/segment-test.ts:33:23)
           at Object.next (/srv/flagsmith/e2e/tests/segment-test.ts:14:53)
           at fulfilled (/srv/flagsmith/e2e/tests/segment-test.ts:5:58)

override_ordering.append("-has_segment_override")
if identity_id := query_data.get("identity"):
queryset = queryset.annotate(
has_identity_override=Exists(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So here, this would make a subquery for each feature row right? I'm comparing to the segment query just above that have a unique_constraint on (feature, segment_id, environment_id), there might be a slight performance difference?
I personally think it's negligeable given that feature limit is 400 per project (~max overrides we can expect) but do you have an idea of the perf loss, how we could measaure it and at what point it could become problematic?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TL;DR: we should be safe.


So here, this would make a subquery for each feature row right?

Yes, this renders to a subquery in SQL, and translates to a hashed subplan — which roughly means that it executes the subquery only once rather than against each row.

I'm comparing to the segment query just above that have a unique_constraint on (feature, segment_id, environment_id), there might be a slight performance difference?

In theory, as we're crossing tuples using foreign keys as anchors, the RDBMS should make the best use of each B-tree index associated to them.

I personally think it's negligeable given that feature limit is 400 per project (~max overrides we can expect) but do you have an idea of the perf loss, how we could measaure it and at what point it could become problematic?

The added effort of these conditional filters should cause minimal / negligible performance impact overall. I expect neither data frame size nor query limit to influence performance as we scale.

Here's an example output of explain analyze that illustrates an hypothetical query plan containing both subqueries.

Sort  (cost=98.11..98.14 rows=12 width=30) (actual time=0.753..0.764 rows=212 loops=1)
    Sort Key: ((hashed SubPlan 2)) DESC, ((hashed SubPlan 4)) DESC, f.name
    Sort Method: quicksort  Memory: 38kB
    Buffers: shared hit=177
    ->  Index Scan using features_feature_project_id_72859830 on features_feature f  (cost=0.42..97.89 rows=12 width=30) (actual time=0.119..0.364 rows=212 loops=1)
          Index Cond: (project_id = <project_id>)
          Buffers: shared hit=169
          SubPlan 2
            ->  Index Scan using features_featuresegment_segment_id_64602469 on features_featuresegment fs  (cost=0.29..3.63 rows=1 width=4) (actual time=0.022..0.026 rows=2 loops=1)
                  Index Cond: (segment_id = <segment_id>)
                  Filter: (environment_id = <environment_id>)
                  Buffers: shared hit=4
          SubPlan 4
            ->  Index Scan using features_featurestate_identity_id_8229f26c on features_featurestate fst  (cost=0.42..8.41 rows=8 width=4) (actual time=0.035..0.035 rows=0 loops=1)
                  Index Cond: (identity_id = <identity_id>)
                  Buffers: shared hit=3
  Planning Time: 1.122 ms
  Execution Time: 0.845 ms

@Zaimwa9
Copy link
Contributor

Zaimwa9 commented Feb 18, 2026

Looks like legit as persistent and reproducible:

  • Go to identities tab
  • Select any identity
"GET /api/v1/projects/353/features/?page=1&environment=438&page_size=50&is_archived=false&is_enabled=null&page=1&search=&sort=%5Bobject%20Object%5D&tag_strategy=INTERSECTION&value_search=&sort_direction=ASC&sort_field=name&identity=b27ff89d-4f0e-4b05-8f0e-85eeefdff267&sort_field=name&sort_direction=ASC HTTP/1.1" 400 45

required=False,
help_text="Integer ID of the segment to retrieve segment overrides for.",
)
identity = serializers.IntegerField(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes ok, that's working when identities are stored in postgres, but when using edge identities, it passes an UUID.

I'll post a full comment with my findings as we'll need to have 2 implementations

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you confirm you were having integer identity.id in the screenshot you shared ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. It was in my local environment.

Copy link
Contributor

@Zaimwa9 Zaimwa9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So my finding after tracking the E2E failure is that we need 2 distinct approaches depending on using edge identities or storing them in postgres.
The current implementation is all good for postgres, even though the frontend should not pass the identity if using edge (we have an utils that is already used to call the edge endpoints).

For edge:
It's actually simpler, as the information already exists. It's coming from
/api/v1/environments/{env_key}/edge-identities/{identity_as_uuid}/edge-featurestates/all/

and the response is as an array as follows:

[
    {
        "feature": {
            "id": 174685,
            "name": "my_go_flag",
            "type": "STANDARD"
        },
        "enabled": true,
        "feature_state_value": null,
        "overridden_by": null,
        "segment": null,
        "multivariate_feature_state_values": []
    },
    {
        "feature": {
            "id": 174687,
            "name": "welcome_page",
            "type": "MULTIVARIATE"
        },
        "enabled": false,
        "feature_state_value": null,
        "overridden_by": null,
        "segment": null,
        "multivariate_feature_state_values": [
            {
                "multivariate_feature_option": {
                    "value": "plop"
                },
                "percentage_allocation": 0
            }
        ]
    }
]

We already have "overridden_by": null, that can be IDENTITY | SEGMENT. So we could even do it from the frontend (i'd be more at peace to add a sort query param and have it done in the backend as it is the case with postgres identities). Don't hesitate to confirm my finding and I let you chose the best path to take

search,
sort,
getServerFilter(filter),
{ ...getServerFilter(filter), identity: id },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we will need to use Utils.getIsEdge() to do something like ...(!isEdge ? { identity: id } : {}),, also for the next useEffect

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turns out the backend will make use of it either way. See ec88a82.

required=False,
help_text="Integer ID of the segment to retrieve segment overrides for.",
)
identity = serializers.IntegerField(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you confirm you were having integer identity.id in the screenshot you shared ?

@emyller emyller marked this pull request as draft February 20, 2026 02:16
@github-actions github-actions bot added fix and removed fix labels Feb 20, 2026
@emyller emyller force-pushed the fix/sort-features-by-overrides branch from f135c7f to bb17797 Compare February 20, 2026 18:00
@github-actions github-actions bot added fix and removed fix labels Feb 20, 2026
@emyller
Copy link
Contributor Author

emyller commented Feb 20, 2026

So my finding after tracking the E2E failure is that we need 2 distinct approaches depending on using edge identities or storing them in postgres.

I really appreciate you digging that up! It has really helped. Brilliant review.

(...) So we could even do it from the frontend (i'd be more at peace to add a sort query param and have it done in the backend as it is the case with postgres identities).

This has connected some dots in my AI agents brain, which led me to ec88a82. I believe this sorting must happen in the backend. Also, it would take the frontend some [unneeded] effort to figure out sorting across paginated data, if I understand things correctly.

Let me know what you think of this new approach.

@emyller emyller force-pushed the fix/sort-features-by-overrides branch from bb17797 to 7da2818 Compare February 20, 2026 18:37
@github-actions github-actions bot added fix and removed fix labels Feb 20, 2026
@emyller emyller force-pushed the fix/sort-features-by-overrides branch from 51f0abb to 2c612c9 Compare February 20, 2026 18:43
@github-actions github-actions bot added fix and removed fix labels Feb 20, 2026
@emyller emyller marked this pull request as ready for review February 20, 2026 19:23
@emyller emyller requested a review from Zaimwa9 February 20, 2026 19:24
@github-actions github-actions bot added fix and removed fix labels Feb 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api Issue related to the REST API fix front-end Issue related to the React Front End Dashboard

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sort features by whether they have a relevant segment or identity override

2 participants